﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;

namespace Mvc.XForms.Utilities {
    public class ExpressionHelper {
		public const char Separator = '_';
		public const char IndexSeparator = '-';
		private Expression originalExp;

		public string ExtractID(Expression exp) {
			this.originalExp = exp;
			return ExtractIDRecursive(exp);
        }

        private string ExtractIDRecursive(Expression exp) {
			if (exp is MemberExpression)
				return ExtractMember(exp as MemberExpression);
			else if (exp is MethodCallExpression)
				return ExtractListAccessor(exp as MethodCallExpression);
			else if (exp is ParameterExpression)
				return string.Empty;
			else if (exp is UnaryExpression)
				return ExtractUnary(exp as UnaryExpression);
			else if (exp == null)
				return string.Empty;
			else
				throw new Exception(string.Format("Error extracting control ID from lambda expression \"{0}\": Expression must only contain members and list index accessors", originalExp));
        }

        private string ExtractMember(MemberExpression exp) {
            var memberName = exp.Member.Name;
            var nextExp = exp.Expression;
			var nextID = ExtractIDRecursive(nextExp);

			if (nextID == string.Empty)
				return memberName;
			else
				return string.Concat(nextID, Separator, memberName);
        }

		private string ExtractUnary(UnaryExpression exp) {
			var memberExp = exp.Operand as MemberExpression;
			var idStr = ExtractIDRecursive(memberExp);
			return idStr;
		}

        private string ExtractListAccessor(MethodCallExpression exp) {
            // Check we have a list accessor call
            if (exp.Method.Name != "get_Item")
				throw new Exception(string.Format("Error extracting control ID from lambda expression \"{0}\": Expression must only contain members and list index accessors", originalExp));

            var listExp = exp.Object as MemberExpression;
            var listName = listExp.Member.Name;
            var nextExp = listExp.Expression;
			var nextID = ExtractIDRecursive(nextExp);
            var listIndex = ExtractListIndex(exp.Arguments[0]);

			if (nextID == string.Empty)
				return string.Concat(listName, IndexSeparator, listIndex);
			else
				return string.Concat(nextID, Separator, listName, IndexSeparator, listIndex);
        }

        private string ExtractListIndex(Expression listAccessorArg) {
            if (listAccessorArg is MemberExpression) {
                // List accessed via an indexer variable
                var listIndexer = listAccessorArg as MemberExpression;
                var listIndexExp = (listIndexer.Expression as ConstantExpression).Value;
                var listFields = listIndexExp.GetType().GetFields();
                var listField = listFields.Single(f => f.FieldType == typeof(int));
                var listIndex = listField.GetValue(listIndexExp).ToString();
                return listIndex;
            } else {
                // List accessed directly by an integer
                var listIndexExp = listAccessorArg as ConstantExpression;
                var listIndex = (Expression.Lambda<Func<int>>(listIndexExp).Compile())().ToString();
                return listIndex;
            }
        }
    }
}
